
#include "dispatcher.h"
#include "request.h"
#include "configuration.h"
#include "plaincontent.h"
#include "htmlcontent.h"
#include "phpcontent.h"
#include "cgicontent.h"
#include "othercontent.h"
#include <fstream>
#include <unistd.h>
#include <magic.h>
#include <sys/types.h>
#include <sys/stat.h>

using namespace std;

Dispatcher::Dispatcher(Request *req) :
	request(req), content(NULL), file_type(Dispatcher::OTHER), state(200), fname(NULL)
{
	string filepath = Configuration::instance().wwwroot() + request->path();

	if (!std::ifstream(filepath.c_str()).good()) {  // not found or not readable
		state = 404;
		return;
	}
	if (isDirectory(filepath)) {
		filepath = (filepath[filepath.size()-1] == '/' ? filepath : filepath + '/') + Configuration::instance().indexFile();
	}

	fname = new char[filepath.size() + 1];
	strncpy(fname, filepath.c_str(), filepath.size());
	fname[filepath.size()] = '\0'; // assure to have a NULL termination

	Dispatcher::FileType type;

	type = isHtmlOrPhp(filepath);

	if (type != Dispatcher::HTML && type != Dispatcher::PHP) {
		magic_t cookie = magic_open(MAGIC_MIME_TYPE);
		magic_load(cookie, NULL);
		mime_type = magic_file(cookie, fname);
		magic_close(cookie);

		if (mime_type.find("text/plain") != string::npos){
			type = Dispatcher::PLAIN;
			mime_type = "text/plain";
		}
		if (isExecutable(filepath)){
			type = Dispatcher::CGI;
			mime_type = "text/html";
		} else type = Dispatcher::OTHER;
	} else {
		mime_type = "text/html";
	}

	switch (type) {
	case Dispatcher::PLAIN:
		content = new PlainContent();
		break;
	case Dispatcher::HTML:
		content = new HtmlContent();
		break;
	case Dispatcher::PHP:
		content = new PhpContent();
		break;
	case Dispatcher::CGI:
		content = new CgiContent();
		break;
	default:
		content = new OtherContent();
	}
	content->setFileName(fname);
	content->setGetQuery(request->getQuery());
	content->setPostQuery(request->postQuery());
}

Dispatcher::~Dispatcher() {
	delete content;
	//delete [] fname;
}

void Dispatcher::sendResponse(){
	if (state != 200) {
		request->sendError(state);
	} else {
		request->addHeader("Content-Type", mime_type.c_str());
		std::string resp = content->getResponse();
		if (file_type == Dispatcher::OTHER) {
			request->addBinaryResponse(resp.c_str(), resp.size());
		} else {
			request->addResponse("%s", resp.c_str());
		}
		request->sendResponse();
	}
}

string Dispatcher::getExtension(string path) {
	return path.substr(path.find_last_of(".") + 1);
}

Dispatcher::FileType Dispatcher::isHtmlOrPhp(string path) {
	string residue = getExtension(path);

	if (residue == "html") {
		return HTML;
	} else if (residue == "php") {
		return PHP;
	} else return OTHER;
}

bool Dispatcher::isExecutable(string path) {
	string ext = getExtension(path);
	bool found = false;
	vector<string> exts = Configuration::instance().allowedexe();
	for (int i = 0, sz = exts.size(); i < sz; i++) {
		if (ext == exts[i]) {
			found = true;
			break;
		}
	}

	if (!found) return false;

	struct stat info;
	stat(path.c_str(), &info);
	uid_t uid = getuid();
	gid_t gid = getgid();

	return ((info.st_uid == uid) && (info.st_mode & S_IXUSR)) ||
			((info.st_gid == gid) && (info.st_mode & S_IXGRP)) ||
			((info.st_mode & S_IXOTH));
}

bool Dispatcher::isDirectory(string path) {
	struct stat info;
	stat(path.c_str(), &info);
	return (S_ISDIR(info.st_mode));
}
